﻿//        Another Demo from Andy L. & MissedMemo.com
// Borrow whatever code seems useful - just don't try to hold
// me responsible for any ill effects. My demos sometimes use
// licensed images which CANNOT legally be copied and reused.

using System;
using System.Linq;
using System.ComponentModel;
using System.Collections.Specialized;
using System.Collections.ObjectModel;

using MissedMemo.MeatAndCheeseDisc.UIHelperClasses;
using MissedMemo.MeatAndCheeseDisc.OrderEntryDataAccess;


namespace MissedMemo.MeatAndCheeseDisc.OrderEntryService.OrderProcessing
{
    public enum ProcessingState { Scheduled, Cooking, Ready, OutForDelivery, Completed }


    // EXTEND local service data proxy with Client-specific features...
    public partial class Order
    {
        private ProcessingState _state = ProcessingState.Scheduled;
        private OrderTimer _timer;

        private TimeSpan _processingTimeExpectedDuration = TimeLimitLookup.GetLimit( "ExpectedDuration" );
        
        public event EventHandler RequestStartCooking;

        private double _costBeforeTax;
        private double _tax;
        private double _taxRate = LookupTaxInfo.GetLocalTaxRate();

        private string _deliveryDriver;

        private bool _hasUnsavedChanges;

        
    #region NowServingNumberGenerator (nested helper class)

        private static class NowServingNumberGenerator
        {
            private readonly static int minNumber = 100;
            private readonly static int maxNumber = 999;

            private static int _next = minNumber;

            internal static int Next()
            {
                if( _next > maxNumber )
                    _next = minNumber;

                return _next++;
            }
        }

    #endregion (Nested helper class)

    #region Properties...

        public ProcessingState ProcessingState
        {
            get { return _state; }
            set
            {
                _state = value;

                if( _state == ProcessingState.Completed )
                {
                    DateTimeOrderCompleted = DateTime.Now;
                    Timer.SuspendTracking();
                }
                else if( DateTimeOrderCompleted.HasValue )
                {
                    DateTimeOrderCompleted = null;
                    Timer.BeginTracking();
                }

                RaisePropertyChanged( "ProcessingState" );
            }
        }

        public OrderTimer Timer
        {
            get { return _timer; }
        }

        public int NumPizzas
        {
            get
            {
                return Items.Count( item => item is Pizza );
            }
        }

        public double CostPreTax
        {
            get { return _costBeforeTax; }
            set
            {
                _costBeforeTax = value;
                RaisePropertyChanged( "CostPreTax" );
            }
        }

        public double Tax
        {
            get { return _tax; }
            set
            {
                _tax = value;
                RaisePropertyChanged( "Tax" );
            }
        }

        public string DeliveryDriver
        {
            get { return _deliveryDriver; }
            set
            {
                _deliveryDriver = value;
                
                if( _deliveryDriver == Employee.DriverUndefined )
                {
                    if( ProcessingState == ProcessingState.OutForDelivery )
                        ProcessingState = ProcessingState.Ready;
                }
                
                else ProcessingState = ProcessingState.OutForDelivery;

                RaisePropertyChanged( "DeliveryDriver" );
            }
        }

        public bool UnsavedChanges
        {
            get { return _hasUnsavedChanges && (Items.Count > 0); }
            set
            {
                _hasUnsavedChanges = value;
                RaisePropertyChanged( "UnsavedChanges" );
            }
        }

    #endregion (Properties)


        public Order()
        {
            NowServingNumber = NowServingNumberGenerator.Next();

            Customer = new Customer();
            Items = new ObservableCollection<OrderItem>();

            DateTimeOrderPlaced = DateTime.Now;
            
            // Limit precision of (initial) expected completion, causing processing to
            // start exactly on the minute mark, simplifying demos and UI testing.

            DateTime dt = DateTimeOrderPlaced + _processingTimeExpectedDuration;
            TimeSpan fractionalMinutes = new TimeSpan( 0, 0, 0, dt.Second, dt.Millisecond );
            DateTimeOrderExpectedComplete = dt.Subtract( fractionalMinutes );

            _timer = new OrderTimer();
            
            TrackPropertyChanges();
        }


        public Order Clone()
        {
            // Perform a shallow copy of all value fields...
            Order copy = (Order)MemberwiseClone();

            // Then, make deep copies of any reference fields...
            copy.Customer = Customer.Clone();
            copy.Items = new ObservableCollection<OrderItem>();

            foreach( OrderItem item in Items )
            {
                copy.Items.Add( item.Clone() );
            }

            // Re-connect events in new instance
            copy.TrackPropertyChanges();

            // Reset 'isDirty' flag
            copy.UnsavedChanges = false;

            return copy;
        }


        private void TrackPropertyChanges()
        {
            _timer.Tick += OnTimerTick;

            // Update billing info. on every add/remove order item
            Items.CollectionChanged += OrderItemsCollectionChanged;
            
            // HACK: Link 'UnsavedChanges' with properties in DATA CONTRACT. Dependency on string ids
            // in methods off the main execution path like this is EXTREMELY fragile!  Alternative?

            PropertyChanged += ( sender, e ) =>
            {
                // Note: Testing UnsavedChanges avoids infinite loop
                bool isWorthSaving = e.PropertyName == "DeliveryRequested"
                                  || e.PropertyName == "DeliveryDriver"
                                  || e.PropertyName == "DateTimeOrderExpectedComplete"
                                  || e.PropertyName == "DateTimeOrderCompleted";

                if( isWorthSaving && UnsavedChanges != true )
                    UnsavedChanges = true;
            };

            // Set 'dirty' flag on any change to Customer data
            Customer.PropertyChanged += ContainedElementPropertyChanged;
            Customer.Address.PropertyChanged += ContainedElementPropertyChanged;
        }


        private void OnTimerTick( object sender, EventArgs e )
        {
            if( IsOrderDueDateTimeInRange() )
            {
                if( ProcessingState == ProcessingState.Scheduled
                 && RequestStartCooking != null )
                {
                    ProcessingState = ProcessingState.Cooking;
                    RequestStartCooking( this, EventArgs.Empty );
                    Timer.BeginTracking();
                }
            }

            // support postponing an order already in progress
            else if( ProcessingState != ProcessingState.Scheduled )
            {
                Timer.SuspendTracking();
                ProcessingState = ProcessingState.Scheduled;
            }
        }


        private bool IsOrderDueDateTimeInRange()
        {
            return ( ( DateTimeOrderExpectedComplete - DateTime.Now ) < _processingTimeExpectedDuration );
        }


        private void OrderItemsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
        {
            CostPreTax = 0;

            foreach( OrderItem item in Items )
                CostPreTax += item.Price;
            
            Tax = CostPreTax * _taxRate;

            TotalCost = CostPreTax + Tax;

            UnsavedChanges = true;
        }


        private void ContainedElementPropertyChanged( object sender, PropertyChangedEventArgs e )
        {
            if( UnsavedChanges != true )
                UnsavedChanges = true;
        }
    }
}
